<?php

class gigJunkieXmlParser
{
    const GJ_API_KEY = '';
    const GJ_API_URL = 'http://api.gigjunkie.net/v0.6/events';

    private $fetched = false;
    private $errorMessage = NULL;
    private $events = array();
    private $resultCount, $returnedPageSize, $returnedPageIndex;
    
    /**
     * GigJunkie query options
     */
    private $pageSize       = 10;
    private $pageIndex      = 0;
    private $query          = NULL;
    private $startDate      = NULL;
    private $endDate        = NULL;
    private $latitude       = NULL;
    private $longitude      = NULL;
    private $maxDistance    = 15;
    private $genre          = NULL;
    private $hideUserAdded  = NULL;
    private $showOnlyRevenue= NULL;
    private $sortBy         = 'date';
    
    
    private $validSortBys = array(
                             'date',
                             'distance'
                            );
                            
    private $validGenres  = array(   
                             'rock-pop',
                             'country-folk',
                             'rap-hip-hop',
                             'jazz-blues',
                             'world',
                             'alternative-indie',
                             'hard-rock-metal',
                             'clubs-dance',
                             'r-and-b-urban-soul',
                             'tribute-bands'
                            );
        
    
    public function __construct($queryString = '')
    {
        /**
         * If supplied, set the query string
         */
        if (strlen($queryString)) {
            $this->setQuery($queryString);
        }
        
        /**
         * Make sure a valid start date is set
         */
        $this->setStartDate(date('Y-m-d'));
    }
    
    
    /**
     * Check that a date is valid (MySQL-style YYYY-MM-DD)
     */
    private function checkValidDate($dateString)
    {
        $dateArray = explode("-", $dateString);
        if (count($dateArray) == 3) {
            if (checkdate($dateArray[1], $dateArray[2], $dateArray[0])) {
                return true;
            }
        }

        return false;
    }
    

    public function setStartDate($startDate)
    {
        if ($this->checkValidDate($startDate) === false) {
            return false;
        }
        else {
            $this->startDate = $startDate;
            $this->fetched = false;
            return true;
        }
    }


    public function setEndDate($endDate)
    {
        if ($this->checkValidDate($endDate) === false) {
            return false;
        }
        else {
            $this->endDate = $endDate;
            $this->fetched = false;
            return true;
        }
    }


    public function setQuery($queryString)
    {
        $this->query = rawurlencode($queryString);
        $this->fetched = false;
    }


    /**
     * Check this is a valid page size value, and set
     */
    public function setPageSize($pageSize)
    {
        if (filter_var($pageSize, FILTER_VALIDATE_INT) !== false) {
            if ($pageSize >= 1 ? $pageSize <= 500 : false) {
                $this->pageSize = $pageSize;
                $this->fetched = false;
                return true;
            }
        }
        
        return false;
    }

    
    /**
     * Check this page index is a positive integer, and set
     */
    public function setPageIndex($pageIndex)
    {
        if (filter_var($pageIndex, FILTER_VALIDATE_INT) !== false) {
            if ($pageIndex >= 0) {
                $this->pageIndex = $pageIndex;
                $this->fetched = false;
                return true;
            }
        }
        
        return false;
    }


    /**
     * Increment the page index
     */
    public function nextPage()
    {
        $this->pageIndex++;
        $this->fetched = false;
        return true;
    }


    /**
     * Check this is a valid latitude, and set.
     */   
    public function setLatitude($latitude)
    {
        if (filter_var($latitude, FILTER_VALIDATE_FLOAT) !== false) {
            if ($latitude >= -90 ? $latitude <= 90 : false) {
                $this->latitude = $latitude;
                $this->fetched = false;
                return true;
            }
        }
        
        return false;
    }


    /**
     * Check this is a valid longitude, and set.
     */   
    public function setLongitude($longitude)
    {
        if (filter_var($longitude, FILTER_VALIDATE_FLOAT) !== false) {
            if ($longitude >= -180 ? $longitude <= 180 : false) {
                $this->longitude = $longitude;
                $this->fetched = false;
                return true;
            }
        }
        
        return false;
    }


    /**
     * Check this distance (from the lat/long point) is a positive float, and set
     */
    public function setDistance($distance)
    {
        if (filter_var($distance, FILTER_VALIDATE_FLOAT) !== false) {
            if ($distance >= 0) {
                $this->maxDistance = $distance;
                $this->fetched = false;
                return true;
            }
        }
        
        return false;
    }


    /**
     * Check this is a valid genre, and set
     */
    public function setGenre($genre)
    {
        if(in_array($genre, $this->validGenres)) {
            $this->genre = $genre;
            $this->fetched = false;
            return true;
        }
        
        return false;
    }


    /**
     * Disallow user added events
     */
    public function hideUserAdded()
    {
        $this->hideUserAdded = true;
        $this->fetched = false;
    }


    /**
     * Allow user added events
     */
    public function displayUserAdded()
    {
        $this->hideUserAdded = NULL;
        $this->fetched = false;
    }


    /**
     * Disallow non-revenue events
     */
    public function hideNonRevenue()
    {
        $this->showOnlyRevenue = true;
        $this->fetched = false;
    }


    /**
     * Allow non-revenue events
     */
    public function showNonRevenue()
    {
        $this->showOnlyRevenue = NULL;
        $this->fetched = false;
    }


    /**
     * Set sort-by field
     */
    public function setSortBy($sortBy)
    {
        if(in_array($sortBy, $this->validSortBys)) {
            $this->sortBy = $sortBy;
            $this->fetched = false;
            return true;
        }
        
        return false;
    }


    /**
     * Just for completeness...
     */
    public function setFormat()
    {
        print("Sorry - this PHP library only works for GigJunkie's XML response format.\n");
        exit;
    }


    /**
     * Construct the API query string (GET string).
     */
    public function constructRequestUrl()
    {
        /**
         * The API key is the only compulsory GET parameter, but
         * we always submit the sort by, and page size/index as well.
         */
        $getString  = "?consumerKey=" . self::GJ_API_KEY;
        $getString .= "&sortBy={$this->sortBy}";
        $getString .= "&pageSize={$this->pageSize}";
        $getString .= "&pageIndex={$this->pageIndex}";

        /**
         * Specify the date range
         */
        $getString .= "&startDate={$this->startDate}";
        if (!is_null($this->endDate)) {
            $getString .= "&endDate={$this->endDate}";
        }
        
        /**
         * Set the query parameter
         */
        if (!is_null($this->query)) {
            $getString .= "&q={$this->query}";
        }
        
        /**
         * Set the geolocation parameters
         */            
        if (!is_null($this->latitude) && !is_null($this->longitude)) {
            $getString .= "&latitude={$this->latitude}";
            $getString .= "&longitude={$this->longitude}";
            $getString .= "&distance={$this->maxDistance}";
        }
        
        /**
         * Set the genre
         */            
        if (!is_null($this->genre)) {
            $getString .= "&genre={$this->genre}";
        }
        
        /**
         * Set the other parameters
         */
        if (!is_null($this->hideUserAdded)) {
            $getString .= "&hideUserAdded=true";
        }
        if (!is_null($this->showOnlyRevenue)) {
            $getString .= "&showOnlyRevenue=true";
        }
        
        return self::GJ_API_URL . $getString;
    }
    
    
    public function parseXML()
    {
        /**
         * Use cURL to fetch the XML from GigJunkie, so that
         * we can catch connection problems.
         */
        $curlHandle = curl_init();
        curl_setopt($curlHandle, CURLOPT_URL, $this->constructRequestUrl());
        curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, true);
        $fetchedXml = curl_exec($curlHandle);
        
        if (curl_errno($curlHandle) > 0) {
            $this->errorMessage  = "cURL error " . curl_errno($curlHandle) . ": " . curl_error($curlHandle);
            $this->errorMessage .= "<br />URL: " . $this->constructRequestUrl();
            return false;
        }
        
        /**
         * libxml_use_internal_errors(true) allows us to fetch error
         * information, rather than throwing an ugly PHP error.
         * 
         * http://uk3.php.net/manual/en/function.libxml-use-internal-errors.php
         */
        libxml_use_internal_errors(true);
        $parsed = simplexml_load_string($fetchedXml);
        if (!$parsed) {
            /**
             * There was a problem parsing the XML.
             * Construct and display an error message.
             */
            $xmlError = libxml_get_errors();
            $xmlError = $xmlError[0];
            $this->errorMessage  = "XML error code (" . $xmlError->code . ") ";
            $this->errorMessage .= "on line " . $xmlError->line;
            $this->errorMessage .= ", column " . $xmlError->column;
            $this->errorMessage .= ":<br />" . $xmlError->message;
            $this->errorMessage .= "<br />URL: " . $this->constructRequestUrl();
            return false;
        }
        
        $this->errorMessage = NULL;

        /**
         * Pull out the meta-data for this XML result set.
         * 
         * (It's necessary to cast SimpleXML values / attributes as strings,
         * otherwise another SimpleXML object will be returned.)
         */
        $this->resultCount          = (string) $parsed->events->attributes()->resultCount;
        $this->returnedPageSize     = (string) $parsed->events->attributes()->pageSize;
        $this->returnedPageIndex    = (string) $parsed->events->attributes()->pageIndex;

        /**
         * Reset this parser's events array...
         */
        $this->events = array();
        
        /**
         * ...and iterate over the events in the XML
         */
        foreach ($parsed->events->event as $eventXml) {
            $event = array();
                        
            /**
             * Parse the top-level info
             */
            $event['eventId'] = (string) $eventXml->attributes()->id;
            $event['eventName'] = (string) $eventXml->name;
            $event['gjUrl'] = (string) $eventXml->gjurl;
            $event['KmFromSearchLocation'] = (string) $eventXml->distance;
            
            /**
             * Convert the start time from ISO 8601 date, to unix timestamp
             */
            $event['startTime'] = strtotime($eventXml->doordatetime);
            
            /**
             * Parse the venue info
             */
            $event['venue'] = array();
            $event['venue']['venueId'] = (string) $eventXml->venue->attributes()->id;
            $event['venue']['venueName'] = (string) $eventXml->venue->name;
            $event['venue']['town'] = (string) $eventXml->venue->town;
            $event['venue']['nameAndTown'] = (string) $eventXml->venue->nameandtown;
            $event['venue']['venueGjUrl'] = (string) $eventXml->venue->gjurl;
            $event['venue']['latitude'] = (string) $eventXml->venue->latitude;
            $event['venue']['longitude'] = (string) $eventXml->venue->longitude;
            
            /**
             * Parse the tickets info
             */
            $event['tickets'] = array();
            foreach ($eventXml->tickets->ticket as $ticketXml) {
                $ticket = array();
                $ticket['ticketId'] = (string) $ticketXml->attributes()->id;
                $ticket['price'] = (string) $ticketXml->price;
                $ticket['url'] = (string) $ticketXml->url;
                $ticket['provider'] = (string) $ticketXml->provider;
                $ticket['soldOut'] = (string) $ticketXml->soldout;
                $ticket['ticketsAvailable'] = (string) $ticketXml->ticketsavailable;
                $ticket['isSecondary'] = (string) $ticketXml->issecondary;
                /**
                 * Add this ticket info to the event
                 */
                $event['tickets'][] = $ticket;
            }
            unset($ticket);
            
            /**
             * Parse the artists info
             */
            $event['artists'] = array();
            foreach ($eventXml->artists->artist as $artistXml) {
                $artist = array();
                $artist['artistId'] = (string) $artistXml->attributes()->id;
                $artist['artistName'] = (string) $artistXml->name;
                $artist['artistGjUrl'] = (string) $artistXml->gjurl;
                $artist['isPrimary'] = (string) $artistXml->attributes()->isprimary;
                /**
                 * Add this artist info to the event
                 */
                $event['artists'][] = $artist;
            }
            unset($artist);
            
            $this->events[] = $event;
            unset($event);
        }
        
        return true;
    }
    
    
    public function fetchEvents()
    {
        if (!$this->fetched) {
            if($this->parseXML() === false) {
                return false;
            }
        }
        return $this->events;
    }
    
    
    public function fetchResultCount()
    {
        if (!$this->fetched) {
            if($this->parseXML() === false) {
                return false;
            }
        }
        return $this->resultCount;
    }
    
    
    /**
     * Calculate and return the number of pages of results still to fetch.
     */
    public function morePagesToFetch()
    {
        if (!$this->fetched) {
            if($this->parseXML() === false) {
                return false;
            }
        }
        
        $totalPages = ceil($this->resultCount / $this->returnedPageSize);
        return ($totalPages - ($this->returnedPageIndex + 1));
    }
    

    /**
     * If the XML parser failed, return the error message.
     */
    public function fetchErrorMessage()
    {
        return $this->errorMessage;
    }

}
